package semantic_matching.entity_search.entity_tree;

import com.carrotsearch.randomizedtesting.LifecycleScope;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.orientechnologies.orient.core.sql.OCommandSQL;
import com.orientechnologies.orient.core.storage.impl.local.paginated.wal.OAbstractCheckPointStartRecord;
import com.tinkerpop.blueprints.Vertex;
import com.tinkerpop.blueprints.impls.orient.OrientGraph;
import common.dao.elastic.ElasticSearchDao;
import common.dao.orient.OrientGraphDao;
import common.helper.json.JsonToJavaObject;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.MatchQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHitField;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import query_understanding.query_language.doc_entity_tree.QueryEdge;
import query_understanding.query_language.doc_entity_tree.QueryNode;
import semantic_matching.common.schema.MatchedEntites;
import textprocessing.datasources.enwikipedia.IndexerEnWikiNews;

import java.util.*;

/**
 * Created by julianzliu on 4/25/2017.
 */
public class QueryEdgeSolver {
    static final Logger logger = LoggerFactory.getLogger(QueryEdgeSolver.class);

    ElasticSearchDao searchDao;
    OrientGraphDao orientGraphDao;
    OrientGraph orientGraph;
    IndexerEnWikiNews indexer;

    public QueryEdgeSolver(ElasticSearchDao searchDao, OrientGraphDao orientGraphDao){
        this.orientGraphDao = orientGraphDao;
        this.searchDao = searchDao;
        this.orientGraph = this.orientGraphDao.getGraph();
        this.indexer = new IndexerEnWikiNews();
    }

    public void solveEdge(QueryEdge.BaseEdge edge, Map<QueryEdge.BaseEdge, MatchedEntites> edgesEntities, Map<QueryNode.BaseNode, MatchedEntites> nodesEntities){
        logger.info("[solveEdge] EdgeType = {} , edgeSolvable = {}", edge.edgeType, edge.solveable);
        if(edge.solveable == false){
            System.out.println("\n\n\n\n\n ERROR \n\n\n\n\n");
            return;
        }

        /*********************************************************
         *
         */
        if(edge.edgeType == QueryEdge.EdgeType.OCCUR_EDGE){
            QueryEdge.OccurEdge occurEdge = (QueryEdge.OccurEdge)edge;
            solveOcurrEdge(occurEdge, edgesEntities,nodesEntities);
        }
        else if(edge.edgeType == QueryEdge.EdgeType.YAGO_EDGE){
            QueryEdge.YagoEdge yagoEdge = (QueryEdge.YagoEdge)edge;
            solveYagoEdge(yagoEdge, edgesEntities, nodesEntities);
        }
        else if(edge.edgeType == QueryEdge.EdgeType.TYPE_EDGE){
            QueryEdge.TypeEdge typeEdge = (QueryEdge.TypeEdge)edge;
            solveTypeEdge( typeEdge, edgesEntities, nodesEntities);
        }
    }


    private void solveOcurrEdge(QueryEdge.OccurEdge edge, Map<QueryEdge.BaseEdge, MatchedEntites> edgesEntities, Map<QueryNode.BaseNode, MatchedEntites> nodesEntities){
        /*
        return
         */
        MatchedEntites returnEntities = new MatchedEntites();
        Set<String> retEntities = returnEntities.entities;
        edgesEntities.put(edge, returnEntities);

        // 填充 retEntities
        // DSL
        String dsl = String.format("");
        /*
        {
          "query": {
            "bool": {
              "must": [
                {
                  "match": {
                    "text": "brother"
                  }
                },
                {
                  "match": {
                    "entities": "<Michelle_Obama>"
                  }
                }
              ]
            }
          }
        }
        */

        /*
        input
         */
        QueryNode.BaseNode inNode = edge.in;
        if(!inNode.solved){
            logger.error("[solveYagoEdge] dependency unresolved!");
        }

        Set<String> inEntities = new HashSet<>();
        if(nodesEntities.containsKey( inNode ) ){
            MatchedEntites inNodeEntities = nodesEntities.get( inNode );
            inEntities = new HashSet<>( inNodeEntities.entities );
        }


        BoolQueryBuilder contextQuery = QueryBuilders.boolQuery();

        logger.info("[solveYagoEdge] nodes Size = {}", inEntities.size());
        if(inEntities.size() > 0){
            for(String node: inEntities){
                MatchQueryBuilder entityQuery = QueryBuilders.matchQuery("entities", node);
                contextQuery.must( entityQuery );
            }
        }


        String keywords = edge.text;
        if(inNode.nodeType == QueryNode.NodeType.KEYWORD ){
            keywords += " " + ((QueryNode.KeywordNode)inNode).text;
        }
        if( (keywords.split(" ")).length > 0 ){
            MatchQueryBuilder keywordQuery = QueryBuilders.matchQuery("text", keywords);
            contextQuery.must( keywordQuery );
        }


        SearchResponse response =
                this.searchDao.searchWithApi(this.indexer.es_context_index, this.indexer.es_context_type,
                contextQuery, 0, 10, null, null);

        Set<String> matchedEntities = new HashSet<>();
        SearchHit[] hits = response.getHits().hits();
        for(SearchHit hit:hits){
            JsonNode hit_node = JsonToJavaObject.loadJsonNode( hit.getSourceAsString() );
            ArrayNode entities = (ArrayNode)hit_node.get("entities");
            //SearchHitField field = hit.field("entities");
            //List<Object> entities = field.getValues();
            for(JsonNode entity: entities){
                String entity_name = entity.asText();
                if(inEntities.contains( entity_name )) continue;
                matchedEntities.add( entity_name  );
            }
        }

        retEntities.addAll( matchedEntities );

        logger.info("[solveOcurrEdge] matched entities, size = {}", matchedEntities.size());


    }

    private void solveTypeEdge(QueryEdge.TypeEdge edge,Map<QueryEdge.BaseEdge, MatchedEntites> edgesEntities, Map<QueryNode.BaseNode, MatchedEntites> nodesEntities){
        /*
        return
         */
        MatchedEntites returnEntities = new MatchedEntites();
        Set<String> retEntities = returnEntities.entities;
        edgesEntities.put(edge, returnEntities);

        // 填充 retEntities

    }



    private void solveYagoEdge(QueryEdge.YagoEdge edge,Map<QueryEdge.BaseEdge, MatchedEntites> edgesEntities, Map<QueryNode.BaseNode, MatchedEntites> nodesEntities){
        /*
        return
         */
        MatchedEntites returnEntities = new MatchedEntites();
        Set<String> retEntities = returnEntities.entities;
        edgesEntities.put(edge, returnEntities);
        /*
        input
         */
        QueryNode.BaseNode inNode = edge.in;
        if(!inNode.solved){
            logger.error("[solveYagoEdge] dependency unresolved!");
        }
        MatchedEntites inNodeEntities = nodesEntities.get( inNode );
        List<String> nodes = new ArrayList<>( inNodeEntities.entities );
        String nodes_condiction = "";
        logger.info("[solveYagoEdge] nodes Size = {}", nodes.size());
        if(nodes.size() == 0){
            return;
        }


        nodes_condiction += String.format(" yago_id='%s' ",nodes.get(0));
        for(Integer i = 1; i < nodes.size(); i++){
            nodes_condiction += String.format(" or yago_id='%s' ", nodes.get(i) );
        }
        String edge_condition = String.format(" yago_label = '%s' ", edge.yago_label);
        /*
        match { class : V , as : start, where: (yago_id='<Barack_Obama>')}
        .bothE() { as: edge1, where: (yago_label = '<isMarriedTo>') }
        .bothV() { as: end, where:($matched.start != $currentMatch)}
        return end
         */
        String sql = String.format(
                        "match { class : V , as : start, where: (%s)}"  + "\n"
                        + ".bothE() { as: edge1, where: (%s) }"  + "\n"
                        + ".bothV() { as: end, where:($matched.start != $currentMatch)}"  + "\n"
                        + "return end"  + "\n"
                ,nodes_condiction, edge_condition
        );
        logger.info("[solveYagoEdge] sql = \n{}", sql);
        Iterable<Vertex> vertices = (Iterable<Vertex>) this.orientGraph.command(
                new OCommandSQL(sql )
        ).execute();
        for(Vertex v : vertices){
            Vertex vertex = v.getProperty("end");

            String yago_id = vertex.getProperty("yago_id");
            logger.info("\n[add Entity] vertex = {}, yago_id = {}\n", vertex, yago_id);

            retEntities.add( yago_id );
        }
        logger.info("[solveYagoEdge] answer_size = {}", retEntities.size());
    }
}
